import { PAPU } from ".";

export class ChannelSquare {
    private papu: PAPU;

    private dutyLookup = [
        0, 1, 0, 0, 0, 0, 0, 0,
        0, 1, 1, 0, 0, 0, 0, 0,
        0, 1, 1, 1, 1, 0, 0, 0,
        1, 0, 0, 1, 1, 1, 1, 1
    ];
    private impLookup = [
        1, -1, 0, 0, 0, 0, 0, 0,
        1, 0, -1, 0, 0, 0, 0, 0,
        1, 0, 0, 0, -1, 0, 0, 0,
        -1, 0, 1, 0, 0, 0, 0, 0
    ];

    private sqr1: boolean = null;
    private isEnabled: boolean = null;
    private lengthCounterEnable: boolean = null;
    private sweepActive: boolean = null;
    private envDecayDisable: boolean = null;
    private envDecayLoopEnable: boolean = null;
    private envReset: boolean = null;
    private sweepCarry: boolean = null;
    private updateSweepPeriod: boolean = null;

    private lengthCounter: number = null;
    private sweepCounter: number = null;
    private sweepCounterMax: number = null;
    private sweepMode: number = null;
    private sweepShiftAmount: number = null;
    private envDecayRate: number = null;
    private envDecayCounter: number = null;
    private envVolume: number = null;
    private masterVolume: number = null;
    private dutyMode: number = null;
    private sweepResult: number = null;
    private vol: number = null;

    public progTimerCount: number = null;
    public progTimerMax: number = null;
    public sampleValue: number = null;
    public squareCounter: number = null;

    constructor(papu: PAPU, square1: boolean) {
        this.papu = papu;
        this.sqr1 = square1;

        this.reset();
    }

    reset() {
        this.progTimerCount = 0;
        this.progTimerMax = 0;
        this.lengthCounter = 0;
        this.squareCounter = 0;
        this.sweepCounter = 0;
        this.sweepCounterMax = 0;
        this.sweepMode = 0;
        this.sweepShiftAmount = 0;
        this.envDecayRate = 0;
        this.envDecayCounter = 0;
        this.envVolume = 0;
        this.masterVolume = 0;
        this.dutyMode = 0;
        this.vol = 0;

        this.isEnabled = false;
        this.lengthCounterEnable = false;
        this.sweepActive = false;
        this.sweepCarry = false;
        this.envDecayDisable = false;
        this.envDecayLoopEnable = false;
    }

    clockLengthCounter() {
        if (this.lengthCounterEnable && this.lengthCounter > 0) {
            this.lengthCounter--;
            if (this.lengthCounter === 0) {
                this.updateSampleValue();
            }
        }
    }

    clockEnvDecay() {
        if (this.envReset) {
            // Reset envelope:
            this.envReset = false;
            this.envDecayCounter = this.envDecayRate + 1;
            this.envVolume = 0xF;
        } else if ((--this.envDecayCounter) <= 0) {
            // Normal handling:
            this.envDecayCounter = this.envDecayRate + 1;
            if (this.envVolume > 0) {
                this.envVolume--;
            } else {
                this.envVolume = this.envDecayLoopEnable ? 0xF : 0;
            }
        }

        this.masterVolume = this.envDecayDisable ? this.envDecayRate : this.envVolume;
        this.updateSampleValue();
    }

    clockSweep() {
        if (--this.sweepCounter <= 0) {

            this.sweepCounter = this.sweepCounterMax + 1;
            if (this.sweepActive && this.sweepShiftAmount > 0 && this.progTimerMax > 7) {

                // Calculate result from shifter:
                this.sweepCarry = false;
                if (this.sweepMode === 0) {
                    this.progTimerMax += (this.progTimerMax >> this.sweepShiftAmount);
                    if (this.progTimerMax > 4095) {
                        this.progTimerMax = 4095;
                        this.sweepCarry = true;
                    }
                } else {
                    this.progTimerMax = this.progTimerMax - ((this.progTimerMax >> this.sweepShiftAmount) - (this.sqr1 ? 1 : 0));
                }
            }
        }

        if (this.updateSweepPeriod) {
            this.updateSweepPeriod = false;
            this.sweepCounter = this.sweepCounterMax + 1;
        }
    }

    updateSampleValue() {
        if (this.isEnabled && this.lengthCounter > 0 && this.progTimerMax > 7) {

            if (this.sweepMode === 0 && (this.progTimerMax + (this.progTimerMax >> this.sweepShiftAmount)) > 4095) {
                //if (this.sweepCarry) {
                this.sampleValue = 0;
            } else {
                this.sampleValue = this.masterVolume * this.dutyLookup[(this.dutyMode << 3) + this.squareCounter];
            }
        } else {
            this.sampleValue = 0;
        }
    }

    writeReg(address: number, value: number) {
        const addrAdd = (this.sqr1 ? 0 : 4);
        if (address === 0x4000 + addrAdd) {
            // Volume/Envelope decay:
            this.envDecayDisable = ((value & 0x10) !== 0);
            this.envDecayRate = value & 0xF;
            this.envDecayLoopEnable = ((value & 0x20) !== 0);
            this.dutyMode = (value >> 6) & 0x3;
            this.lengthCounterEnable = ((value & 0x20) === 0);
            this.masterVolume = this.envDecayDisable ? this.envDecayRate : this.envVolume;
            this.updateSampleValue();

        }
        else if (address === 0x4001 + addrAdd) {
            // Sweep:
            this.sweepActive = ((value & 0x80) !== 0);
            this.sweepCounterMax = ((value >> 4) & 7);
            this.sweepMode = (value >> 3) & 1;
            this.sweepShiftAmount = value & 7;
            this.updateSweepPeriod = true;
        }
        else if (address === 0x4002 + addrAdd) {
            // Programmable timer:
            this.progTimerMax &= 0x700;
            this.progTimerMax |= value;
        }
        else if (address === 0x4003 + addrAdd) {
            // Programmable timer, length counter
            this.progTimerMax &= 0xFF;
            this.progTimerMax |= ((value & 0x7) << 8);

            if (this.isEnabled) {
                this.lengthCounter = this.papu.getLengthMax(value & 0xF8);
            }

            this.envReset = true;
        }
    }

    setEnabled(value: boolean) {
        this.isEnabled = value;
        if (!value) {
            this.lengthCounter = 0;
        }
        this.updateSampleValue();
    }

    getLengthStatus() {
        return ((this.lengthCounter === 0 || !this.isEnabled) ? 0 : 1);
    }
};
